Перейти к основному содержимому

6.08. Selenium

Тестировщику Разработчику Аналитику

Selenium

«Машины не думают как люди. Но они могут повторять то, что люди делают — и делать это миллионы раз подряд, не уставая, не ошибаясь в рутине, не отвлекаясь на шум за окном».

1. Интуитивные действия, которые мы совершаем, даже не замечая

Когда человек открывает веб-страницу, он вступает в диалог с интерфейсом. Этот диалог идёт через простые, привычные жесты:

  • указатель мыши перемещается к нужной кнопке — курсор меняет форму, зрительная система подтверждает: «да, это кликабельно»;
  • палец нажимает клавишу — символ появляется в поле ввода;
  • колесо прокрутки вращается — контент сдвигается, открывая новые блоки;
  • вкладки переключаются — внимание переходит с одного контекста к другому;
  • форма отправляется — страница реагирует: загружается, появляется сообщение, меняется URL.

Эти действия кажутся естественными, потому что они отточены годами повседневного использования. Компьютер при этом остаётся пассивным наблюдателем: он отвечает на события, но не инициирует их, если не получит команду. Браузер — это интерпретатор, который реагирует на сигналы от операционной системы: клик мыши → событие click, нажатие клавиш → событие keydown, прокрутка → событие scroll.

То, что для человека — простое движение пальца, для машины — строго формализованный поток цифровых команд. И если эти команды можно записать, повторить, изменить и запустить по расписанию — появляется возможность делегировать рутину.

2. Предел человеческой выносливости

Рассмотрим типичную задачу тестировщика: проверить, как веб-форма реагирует на 100 различных комбинаций логинов и паролей. Вручную это означает:

  • открыть страницу;
  • ввести данные;
  • нажать кнопку;
  • дождаться ответа;
  • зафиксировать результат;
  • очистить форму;
  • повторить — 99 раз.

За первый час внимательность снижается. За второй — начинаются ошибки: пропущенное поле, опечатка в пароле, случайный клик мимо кнопки. За третий — появляется усталость, раздражение, снижение мотивации. Это не недостаток человека. Это физиологическая характеристика человеческого восприятия: мозг устроен так, чтобы искать новизну, а не выполнять идентичные действия. Рутина — не его среда.

Тем временем, машина:

  • способна выполнять миллионы одинаковых операций без изменения качества;
  • фиксирует каждое действие с точностью до миллисекунды;
  • не испытывает усталости, эмоций, отвлекающих факторов;
  • может работать круглосуточно, без перерывов, в любом часовом поясе.

Проблема в одном: машина не знает, что делать — пока ей не опишут последовательность команд на понятном ей языке. Задача инструментов вроде Selenium — дать человеку возможность формализовать поведение пользователя и передать его исполнителю: браузеру.

3. Selenium

Selenium — это не одна программа. Это экосистема, объединяющая стандарты, протоколы, драйверы и библиотеки для программного управления веб-браузерами. Его главная задача — позволить разработчику писать код, который:

  • запускает браузер как отдельный процесс;
  • открывает в нём страницы по URL;
  • находит элементы интерфейса (кнопки, поля, ссылки) по их признакам;
  • имитирует действия пользователя: клики, ввод текста, наведение, прокрутку;
  • считывает состояние страницы: заголовок, URL, тексты, атрибуты, cookies;
  • реагирует на изменения: ожидает появления элемента, проверяет текст сообщения, перехватывает всплывающие окна.

Selenium стал стандартом де-факто в индустрии автоматизированного тестирования веб-приложений. Его поддерживают все основные браузеры: Chrome, Firefox, Safari, Edge. Он интегрируется с современными фреймворками тестирования (pytest, JUnit, TestNG), системами непрерывной интеграции (Jenkins, GitLab CI, GitHub Actions) и облачными платформами (Sauce Labs, BrowserStack, LambdaTest). Его используют крупнейшие технологические компании — от Google и Microsoft до Яндекса и Сбера.

Это мост между логикой программы и живым поведением пользователя в браузере.

4. Краткая история

История Selenium началась в 2004 году. Джейсон Хаггинс, инженер в компании ThoughtWorks, разрабатывал веб-приложение и столкнулся с рутиной ручного тестирования. Чтобы ускорить процесс, он создал внутренний скрипт на JavaScript, который внедрялся в страницу и управлял ею «изнутри» — это был Selenium Core.

Позже, поняв масштаб проблемы, он добавил серверную часть — Selenium RC (Remote Control), который позволял управлять браузером из внешней программы на любом языке. Это был прорыв: тесты перестали быть привязанными к JavaScript и браузеру, в котором они выполнялись.

В 2009 году проект объединился с инструментом WebDriver (разрабатывавшимся Саймоном Стюартом в Google), чтобы преодолеть ограничения архитектуры RC. Результатом стала Selenium WebDriver — более надёжная, прямая и безопасная модель взаимодействия с браузером через нативные API. Именно она легла в основу современного Selenium.

В 2018 году W3C утвердил WebDriver как официальный стандарт для автоматизации браузеров. Selenium стал его наиболее полной и зрелой реализацией.

Эта эволюция — от хака до стандарта — отражает фундаментальную потребность индустрии: иметь единый, открытый, независимый способ управлять браузером. Selenium её удовлетворил.

5. Где применяется Selenium

Основная и самая зрелая область применения — автоматизированное тестирование веб-приложений. Здесь Selenium проверяет:

  • корректность отображения элементов и макетов;
  • логику взаимодействия: кнопки реагируют, формы валидируются, переходы работают;
  • интеграцию с бэкендом: данные сохраняются, ошибки обрабатываются;
  • регрессии: изменения в коде не сломали уже работающий функционал.

Автоматические тесты запускаются при каждом коммите в репозиторий, обеспечивая «безопасную сеть» для разработки.

Вторая важная сфера — сбор данных с динамических веб-сайтов. Многие современные приложения построены как SPA (Single Page Applications): React, Angular, Vue. В них контент не приходит в виде готового HTML, а формируется на клиенте — в браузере — с помощью JavaScript. Классические методы парсинга (например, requests + BeautifulSoup) получают «пустую оболочку», в то время как Selenium:

  • дожидается полной инициализации приложения;
  • прокручивает страницу, чтобы активировать lazy-loading;
  • нажимает кнопки «Загрузить ещё»;
  • извлекает данные уже после их появления в DOM.

Это позволяет собирать информацию с маркетплейсов, новостных агрегаторов, карт, календарей — везде, где данные подгружаются после события.

Третья группа задач — имитация пользовательского поведения:

  • нагрузочное тестирование: запустить 10, 100, 1000 сессий, каждая из которых ведёт себя как реальный пользователь (в сочетании с инструментами вроде Selenium Grid или Selenoid);
  • демонстрационные скрипты: автоматическая запись прохождения тура по продукту для презентаций;
  • аудит доступности: проверка, как интерфейс ведёт себя при навигации с клавиатуры;
  • мониторинг: регулярная проверка, доступен ли сайт, корректно ли отображается критический элемент, изменилась ли цена.

Четвёртая, более деликатная область — персональная автоматизация:

  • заполнение часто повторяющихся форм (анкеты, отчёты, заявки);
  • авторизация в личных кабинетах (при условии соблюдения законов и условий использования).

Здесь важна ответственность: автоматизация личных задач — это удобство, но только если она не нарушает правила сервиса и не ставит под угрозу безопасность аккаунта.

6. Что Selenium НЕ умеет делать

Selenium работает исключительно с веб-браузерами. Он не взаимодействует с десктопными приложениями (Word, Photoshop, 1С), мобильными нативными приложениями (если только они не встроены в WebView и не управляются через Appium), терминальными утилитами.

Selenium не предназначен для обхода систем защиты. Такие технологии, как Cloudflare, reCAPTCHA, hCaptcha, анализируют поведение пользователя — скорость ввода, траекторию мыши, отпечаток браузера. Selenium в базовой конфигурации легко детектируется, потому что:

  • у него отсутствует естественная «дрожь» мыши;
  • ввод текста мгновенный, без пауз;
  • в DOM присутствует свойство navigator.webdriver = true;
  • отсутствуют события mousemove, mousedown, mouseup между click.

Обход таких систем требует дополнительных мер — и часто выходит за рамки легального использования.

Selenium не заменяет API. Если у сервиса есть публичный API — это всегда предпочтительный способ взаимодействия. API быстрее, стабильнее, менее ресурсоёмок, и его поведение документировано. Парсинг через Selenium — это вторичный, резервный путь, когда API отсутствует или недостаточен.

7. Архитектура Selenium

Чтобы понять, почему Selenium так устроен, нужно разобрать цепочку управления от строки кода до изменения пикселей на экране.

7.1. Браузер

Современный браузер — это целый многопроцессный оркестр:

  • браузерный процесс управляет окнами, вкладками, сетью;
  • процесс рендеринга (по одному на вкладку или домен) — строит DOM, применяет CSS, выполняет JavaScript;
  • процесс GPU — отвечает за анимации и отрисовку;
  • процесс утилит — работает с файлами, прокси, шифрованием.

Эта изоляция повышает безопасность и стабильность: если одна вкладка зависнет — остальные продолжат работу. Но она же блокирует прямой доступ извне: внешняя программа не может просто «нажать кнопку» в чужом процессе — это нарушение модели безопасности операционной системы.

7.2. Протокол WebDriver

Чтобы решить эту проблему, был разработан WebDriver — стандарт, описывающий, как управлять пользовательским агентом через HTTP-запросы. Это не привязка к конкретному языку или браузеру. Это спецификация: какие команды существуют (get, findElement, click, sendKeys), какие параметры они принимают, какие ответы возвращают.

Каждая команда — это HTTP-запрос в формате JSON к локальному серверу. Например, открытие страницы:

POST /session/{session_id}/url
{
"url": "https://example.com"
}

Ответ — статус выполнения и, при успехе, пустое тело.

Такой подход делает систему расширяемой: любой браузер может реализовать сервер WebDriver, и любой клиент может отправлять ему команды.

7.3. Драйвер браузера

Драйвер (например, chromedriver) — это небольшая программа-сервер, разрабатываемая командой браузера (Google для Chrome, Mozilla для Firefox). Она:

  • запускается отдельным процессом на той же машине;
  • открывает TCP-порт и принимает HTTP-запросы по протоколу WebDriver;
  • переводит эти запросы в нативные вызовы браузера через внутренние API (DevTools Protocol для Chrome, Marionette для Firefox);
  • возвращает результаты в формате JSON.

Драйвер — это «легитимный агент». Он работает с полными правами, потому что запускается явно пользователем (или тестовой системой), а не внедряется извне. Это решение балансирует между безопасностью и функциональностью.

7.4. Библиотека клиента

Код на Python, Java, C# не должен формировать JSON-запросы вручную. Библиотека selenium берёт на себя:

  • создание HTTP-запросов;
  • обработку ответов;
  • преобразование данных (например, JSON-описание элемента → объект WebElement);
  • управление сессиями и ошибками.

Класс webdriver.Chrome() — это фабрика, которая:

  1. запускает chromedriver как дочерний процесс;
  2. устанавливает соединение с его HTTP-сервером;
  3. создаёт объект WebDriver, инкапсулирующий соединение.

Всё это скрыто от пользователя. Разработчик видит прозрачный интерфейс: driver.get(url), driver.find_element(...), element.click().

7.5. Схема взаимодействия

[ Ваш код на Python ]

[ Библиотека selenium — формирует команду ]

[ HTTP-запрос по WebDriver-протоколу → localhost:port ]

[ chromedriver — получает запрос, проверяет его ]

[ Вызов Chrome DevTools Protocol через внутренние API ]

[ Движок Chrome — изменяет DOM, генерирует события, перерисовывает ]

[ Ответ от chromedriver → JSON-статус ]

[ Библиотека selenium — создаёт объект WebElement / возвращает данные ]

[ Ваш код получает результат и продолжает работу ]

Эта архитектура объясняет, почему:

  • драйвер должен быть той же версии, что и браузер (он использует внутренние API, которые могут меняться);
  • Selenium не может работать без запущенного драйвера;
  • каждый экземпляр webdriver.Chrome() — это полноценный браузер, со своими cookies, кэшем и сессией.

8. Подготовка среды

Автоматизация начинается не с кода, а с доверия к инфраструктуре. Если драйвер не той версии, если библиотека устарела, если браузер обновился без уведомления — скрипт упадёт на первом же шаге. Чтобы этого не произошло, нужна устойчивая, самодостаточная и легко воспроизводимая среда.

8.1. Базовый стек

Для работы с Selenium в 2025 году минимальный набор выглядит так:

  • Python 3.8 или новее — язык с чётким синтаксисом, богатой экосистемой и отличной поддержкой Selenium. Версии ниже 3.8 уже не поддерживаются сообществом (например, webdriver-manager требует Python ≥ 3.7, но 3.8 — более безопасный выбор).
  • pip — стандартный пакетный менеджер Python. Он идёт в комплекте с официальной сборкой интерпретатора.
  • Браузер — Chrome или Firefox. Они имеют наиболее стабильные и быстро обновляемые драйверы. Safari и Edge тоже поддерживаются, но с оговорками: Safari требует включения «Разрешить удалённую автоматизацию» в настройках, Edge — дополнительной настройки политик в корпоративной среде.
  • Интернет-соединение — для автоматической загрузки драйверов.

Всё остальное — selenium, webdriver-manager, драйверы — устанавливается программно, а не вручную. Это ключевой принцип надёжности.

8.2. Установка библиотек

Первый шаг — установка клиентской библиотеки Selenium. Выполняем в терминале:

pip install selenium

Эта команда загружает и устанавливает последнюю стабильную версию пакета selenium из PyPI (Python Package Index). На момент 2025 года это — Selenium 4.x, в котором:

  • полная поддержка стандарта W3C WebDriver;
  • встроенная поддержка относительной навигации (driver.back(), driver.forward());
  • улучшенный API для работы с iframe и вкладками;
  • нативные методы для прокрутки (scroll_by_amount, scroll_to_element);
  • более строгая типизация (совместимость с mypy).

Проверить установку можно так:

python -c "import selenium; print(selenium.__version__)"

Если выводится версия (например, 4.24.0) — всё в порядке.

8.3. Проблема совместимости

Драйвер — это не «универсальный ключ». Это точный переводчик между протоколом WebDriver и внутренним API конкретной версии браузера. Chrome 131 работает с новыми фичами DevTools Protocol, которых нет в Chrome 128. chromedriver 131 знает об этих фичах. chromedriver 128 — нет. Попытка использовать несовместимые версии приводит к одной из самых частых ошибок:

SessionNotCreatedException: session not created: This version of ChromeDriver 
only supports Chrome version 128
Current browser version is 131.0.6778.85

Раньше разработчики решали это вручную:

  1. Открыть chrome://version → скопировать версию (например, 131.0.6778.85);
  2. Перейти на https://chromedriver.chromium.org;
  3. Найти архив, соответствующий мажорной версии (131);
  4. Скачать, распаковать, переместить chromedriver в проект или в PATH;
  5. Повторить при каждом обновлении браузера.

Это утомительно, подвержено ошибкам и не масштабируется. Особенно в командах или CI-средах.

8.4. webdriver-manager

webdriver-manager — это вспомогательная библиотека, которая:

  • определяет текущую версию установленного браузера;
  • находит подходящую версию драйвера на официальных зеркалах (Google, Mozilla);
  • скачивает её в кэш (обычно ~/.wdm/drivers/);
  • возвращает путь к исполняемому файлу.

Установка:

pip install webdriver-manager

После этого код инициализации браузера становится устойчивым к обновлениям:

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager

# Создаём сервис, который управляет жизненным циклом chromedriver
service = Service(ChromeDriverManager().install())

# Запускаем браузер
driver = webdriver.Chrome(service=service)

Что происходит внутри:

  1. ChromeDriverManager().install():

    • вызывает google-chrome --version (или аналог для Firefox/Edge);
    • парсит вывод: Google Chrome 131.0.6778.85;
    • извлекает мажорную версию 131;
    • проверяет локальный кэш: есть ли chromedriver для версии 131;
    • если нет — скачивает с https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/...;
    • распаковывает и возвращает путь: /home/user/.wdm/drivers/chromedriver/linux64/131/chromedriver.
  2. Service(...) оборачивает этот путь в объект, управляющий запуском и остановкой драйвера.

  3. webdriver.Chrome(service=service):

    • запускает chromedriver как дочерний процесс;
    • устанавливает HTTP-соединение с ним;
    • создаёт сессию и возвращает управление.

Этот подход решает 95% проблем с окружением. Он работает на Windows, macOS, Linux, в Docker-контейнерах (при условии наличия браузера), в CI/CD-пайплайнах. Это рекомендуемый способ в 2025 году.

Примечание для Linux (Arch, Ubuntu и др.)
Если Chrome установлен через пакетный менеджер (yay -S google-chrome, sudo apt install google-chrome-stable), его версия обновляется вместе с системой. webdriver-manager корректно определяет её через google-chrome --version.
Для Firefox — аналогично: geckodriver управляется через GeckoDriverManager().
В headless-средах (например, сервер без GUI) важно установить xvfb или использовать аргумент --headless=new — об этом позже.

8.5. Явное указание пути

В редких случаях (например, в закрытой сети без интернета, или при строгих требованиях к версиям) используется ручная установка драйвера. Алгоритм:

  1. Скачать драйвер вручную и положить, например, в папку project/drivers/;
  2. Указать путь явно:
service = Service("./drivers/chromedriver")
driver = webdriver.Chrome(service=service)

Этот способ даёт полный контроль, но требует ручного сопровождения. Лучше применять его только в исключительных обстоятельствах — например, когда политика безопасности запрещает внешние HTTP-запросы.

8.6. Добавление в PATH

Раньше драйвер копировали в системную папку (/usr/local/bin на Linux, C:\Windows\System32 на Windows), чтобы он был доступен «из любого места». Тогда инициализация упрощалась:

driver = webdriver.Chrome()  # без Service — ищет chromedriver в PATH

Сейчас это не рекомендуется, потому что:

  • обновление браузера может сломать совместимость без предупреждения;
  • в проекте может быть несколько тестов с разными требованиями к версиям;
  • в CI-средах сложно гарантировать чистоту PATH.

Лучше держать драйверы в изолированном кэше (~/.wdm/) и управлять ими через webdriver-manager.

8.7. Hello, Browser

Теперь — самый простой, но критически важный скрипт:

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager

with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver:
driver.get("https://example.com")
print("Заголовок страницы:", driver.title)

Разберём каждую строчку.

Конструкция with

with — это контекстный менеджер. Он автоматически вызывает driver.quit() при выходе из блока — даже если произошла ошибка. Это важно, потому что:

  • каждый webdriver.Chrome() запускает полноценный браузер — процесс, потребляющий 300–500 МБ ОЗУ;
  • при аварийном завершении (ошибка, Ctrl+C) процесс может остаться «висеть»;
  • накопление таких «зомби» приводит к перегрузке памяти, особенно при запуске сотен тестов.

Без with пришлось бы писать:

driver = webdriver.Chrome(...)
try:
# ... работа
finally:
driver.quit() # обязательно!

with делает это неявно, надёжно и элегантно.

Метод driver.get(url)

get() — это команда «открой страницу и дождись, пока она полностью загрузится». Под «полностью» понимается:

  • HTML-документ построен;
  • все синхронные скрипты выполнены;
  • DOM готов к взаимодействию.

Он не ждёт асинхронных операций: AJAX-запросов, динамической подгрузки контента, анимаций. Это частый источник заблуждений: после get() элемент может ещё отсутствовать. Об этом — в разделе про ожидания.

Свойство driver.title

title возвращает содержимое тега <title> из <head>. Это самый простой способ убедиться, что:

  • страница действительно загрузилась;
  • мы находимся в правильном месте (например, не попали на страницу ошибки 404);
  • браузер отвечает на команды.

Вывод:

Заголовок страницы: Example Domain

Если вместо этого — ошибка или пустая строка — значит, что-то пошло не так: сетевая проблема, блокировка, таймаут. Это наш первый сигнал о неисправности.

8.8. Что происходит при первом запуске

Проследим за жизненным циклом:

  1. Интерпретатор Python запускает скрипт;
  2. ChromeDriverManager().install() — определяет версию Chrome, скачивает драйвер (если нужно), возвращает путь;
  3. Service(...) создаёт объект, управляющий драйвером;
  4. webdriver.Chrome(...):
    • запускает chromedriver как отдельный процесс;
    • chromedriver стартует локальный HTTP-сервер (например, на порту 54321);
    • создаёт сессию, получает session_id;
    • открывает новое окно браузера (с чистым профилем, без расширений, без истории);
  5. driver.get("https://example.com") → HTTP-POST на /session/{id}/url;
  6. Chrome:
    • устанавливает соединение с example.com;
    • получает HTML-ответ;
    • строит DOM-дерево;
    • выполняет скрипты (на этом сайте их нет);
    • обновляет заголовок окна;
  7. driver.title → HTTP-GET /session/{id}/title → получает "Example Domain".

Всё это происходит за 2–4 секунды — и в этом времени заключена вся мощь интеграции: от Python-кода до пикселей на экране.


9. Поиск элементов

Когда человек смотрит на страницу, его мозг мгновенно распознаёт объекты:
«Вот поле ввода email — оно подписано “Электронная почта”, имеет рамку, курсор мигает внутри».
Машина этого не умеет. Для неё страница — это древовидная структура HTML-элементов, каждый со своими атрибутами, текстом и положением в иерархии.

9.1. DOM

DOM (Document Object Model) — это программная модель HTML-документа. Браузер строит его по мере загрузки:

  • <html> — корень;
  • внутри <head> и <body>;
  • <body> содержит <div>, <form>, <input>, <button> и так далее — с вложенностью до десятков уровней.

Каждый элемент — это объект со свойствами:

  • tagName — имя тега (input, button, a);
  • id, class, name, placeholder, type — атрибуты;
  • textContent — видимый текст;
  • children — дочерние элементы;
  • parentElement — родитель.

Selenium не «смотрит на экран». Он запрашивает у браузера описание элементов по заданным критериям. Это как запрос в базу данных: «Найди все <input> с name="password"».


10. Локаторы

Selenium предлагает семь стратегий поиска — от самых простых до самых мощных. Порядок ниже — не случайный. Это иерархия надёжности и предпочтительности.

10.1. By.ID

Атрибут id в HTML должен быть уникальным на странице. Это требование спецификации. Хорошо спроектированный интерфейс использует id для ключевых элементов: форм, кнопок отправки, основных блоков.

Пример HTML:

<input type="email" id="user-email" placeholder="Введите email">

Код:

email_field = driver.find_element(By.ID, "user-email")

Преимущества:

  • однозначность: если id задан верно — найдётся ровно один элемент;
  • скорость: браузер использует хеш-таблицу для поиска по id, это O(1);
  • читаемость: имя user-email сразу говорит о назначении.

Рекомендация: если у элемента есть id — используйте его. Это первый и главный выбор.


10.2. By.NAME

Атрибут name широко используется в формах, особенно в legacy-коде. Он не обязан быть уникальным, но часто уникален в пределах одной формы.

Пример:

<input type="password" name="pass">

Код:

password_field = driver.find_element(By.NAME, "pass")

Когда name подходит:

  • элемент входит в форму, и name соответствует полю в данных отправки (application/x-www-form-urlencoded);
  • id не задан, но name стабилен и осмыслен.

Осторожно: если на странице несколько элементов с одинаковым namefind_element() вернёт первый. Это может быть не тот элемент, что ожидается.


10.3. By.CLASS_NAME

Классы (class) используются для стилизации и поведения. Один элемент может иметь несколько классов: class="btn btn-primary submit".

Пример:

<button class="btn btn-large confirm">Подтвердить</button>

Код:

button = driver.find_element(By.CLASS_NAME, "confirm")

Важные нюансы:

  • By.CLASS_NAME ищет ровно один класс. Если написать "btn btn-large" — поиск не сработает (пробел интерпретируется как часть имени).
  • если класс динамический (например, loading_abc123), он может меняться при каждом обновлении — такой локатор хрупок.
  • классы часто дублируются: кнопка «Отмена» и «Подтвердить» могут иметь class="btn".

Лучше использовать By.CLASS_NAME, только если:

  • класс уникален и семантичен (confirm, delete-warning);
  • он не генерируется автоматически (например, не sc-fjdhpX из CSS-in-JS).

10.4. By.TAG_NAME

Ищет по имени HTML-тега: a, button, input, h1.

Пример:

first_link = driver.find_element(By.TAG_NAME, "a")

Применение:

  • получение всех ссылок на странице (find_elements(By.TAG_NAME, "a"));
  • работа с уникальными тегами: <title>, <meta>, <base>;
  • быстрый доступ к <body> для прокрутки или ввода глобальных горячих клавиш.

Ограничение: почти никогда не подходит для точечного взаимодействия. На странице десятки <div> и <span> — какой из них нужен?


Эти стратегии ищут <a>-элементы по видимому тексту.

Полный совпадение:

faq_link = driver.find_element(By.LINK_TEXT, "Часто задаваемые вопросы")

Частичное совпадение:

support_link = driver.find_element(By.PARTIAL_LINK_TEXT, "поддержк")

Особенности:

  • работает только с <a>-тегами;
  • чувствителен к пробелам, регистру, переносам строк;
  • ломается при изменении формулировки: «FAQ» вместо «Часто задаваемые вопросы».

Применяйте, когда текст стабилен и семантичен (например, в навигационном меню), но избегайте в динамических интерфейсах.


10.6. By.CSS_SELECTOR

CSS-селекторы — язык, изначально созданный для стилизации, но идеально подходящий для поиска. Они читаются слева направо, от общего к частному, и поддерживают:

  • поиск по id: #user-email;
  • по классу: .btn;
  • по атрибуту: [type="submit"], [placeholder*="email"];
  • комбинации: form#login input[name="email"];
  • псевдоклассы: :first-child, :nth-of-type(2).

Примеры:

# Кнопка с классом "submit" внутри формы с id="login"
driver.find_element(By.CSS_SELECTOR, "form#login .submit")

# Поле ввода, в placeholder которого есть "email"
driver.find_element(By.CSS_SELECTOR, "input[placeholder*='email']")

# Первая кнопка в теле документа
driver.find_element(By.CSS_SELECTOR, "body button:first-of-type")

Преимущества CSS-селекторов:

  • лаконичность и выразительность;
  • поддержка большинства современных браузеров на уровне движка (очень быстро);
  • не зависят от внутренней структуры DOM так сильно, как XPATH;
  • интуитивно понятны разработчикам фронтенда.

Рекомендация: если id и name недоступны — CSS_SELECTOR — ваш основной инструмент. Он надёжнее XPATH в большинстве случаев.


10.7. By.XPATH

XPATH (XML Path Language) — мощный язык навигации по дереву элементов. Он позволяет:

  • искать по тексту: //button[text()='Войти'];
  • искать по предкам/потомкам: //div[@class='card']//input;
  • использовать функции: contains(), starts-with(), normalize-space().

Примеры:

# Кнопка с текстом "Купить", внутри div с классом "product"
button = driver.find_element(By.XPATH, "//div[contains(@class, 'product')]//button[text()='Купить']")

# Поле ввода, placeholder которого содержит "email"
field = driver.find_element(By.XPATH, "//input[contains(@placeholder, 'email')]")

# Любой элемент с текстом "Успешно"
message = driver.find_element(By.XPATH, "//*[text()='Успешно']")

Почему XPATH хрупок:

  • структура DOM часто меняется: добавляются обёртки <div>, меняются классы, перестраивается вложенность;
  • //div//div//button может найти не ту кнопку, если появится второй блок;
  • абсолютные пути (/html/body/div[2]/form/input[1]) ломаются при малейшем изменении разметки.

Когда XPATH неизбежен:

  • элемент не имеет id, name, устойчивого класса;
  • нужно найти по точному тексту — это единственный способ;
  • интерфейс построен фреймворком, генерирующим нестабильные class (например, Angular без data-testid).

Правило: используйте XPATH, только когда другие методы не работают. И делайте его как можно короче и семантичнее — через contains(@class, '...'), а не через div[3]/span[2]/button.


11. find_element() и find_elements()

Selenium предоставляет два фундаментально разных метода:

МетодЧто возвращаетЕсли элемент найденЕсли элемент не найден
find_element()один объект WebElementпервый подходящий элементNoSuchElementException
find_elements()список list[WebElement]список из 1+ элементовпустой список []

11.1. Когда использовать find_element()

  • Вы уверены, что элемент должен быть один (например, id="submit").
  • Вы готовы обработать исключение, если элемент отсутствует (например, в тестах: «если кнопки нет — тест упал»).
  • Вы работаете с критически важным элементом: форма, основная кнопка, заголовок.

Пример (тест на наличие ошибки):

try:
error_msg = driver.find_element(By.CLASS_NAME, "error")
assert "Неверный пароль" in error_msg.text
except NoSuchElementException:
assert False, "Ожидаемое сообщение об ошибке не появилось"

11.2. Когда использовать find_elements()

  • Элементов может быть ноль, один или много (карточки товаров, пункты меню, чекбоксы).
  • Вы хотите проверить наличие/отсутствие без исключений.
  • Вам нужно пройти по всем элементам (например, собрать все цены).

Пример (сбор списка товаров):

cards = driver.find_elements(By.CSS_SELECTOR, ".product-card")
print(f"Найдено товаров: {len(cards)}")
for card in cards:
title = card.find_element(By.CLASS_NAME, "title").text
price = card.find_element(By.CLASS_NAME, "price").text
print(f"{title}{price}")

Пример (проверка отсутствия всплывающего окна):

popups = driver.find_elements(By.ID, "ad-banner")
if len(popups) == 0:
print("Рекламный баннер не отображается — хорошо.")

Это гораздо надёжнее, чем try/except для простой проверки существования.


12. Инструменты разработчика

Нельзя писать локаторы вслепую. Браузер уже содержит всё необходимое для их отладки.

12.1. Как открыть панель Elements

  • F12 (Windows/Linux) или Cmd+Opt+I (macOS);
  • ПКМ → «Просмотреть код» на любом элементе;
  • через меню: Ещё → Дополнительные инструменты → Инструменты разработчика.

Вкладка Elements показывает текущее DOM-дерево — то, что видит Selenium.

12.2. Поиск по локатору внутри панели

  • Нажмите Ctrl+F (Cmd+F) — появится строка поиска;
  • Введите:
    • #user-email — найдёт по id;
    • .btn — по классу;
    • input[name='email'] — по CSS;
    • //input[@placeholder='Логин'] — по XPATH.

Если найдено — отобразится количество совпадений и подсветка. Это мгновенная проверка перед написанием кода.

12.3. Копирование селекторов

ПКМ на элементе → Copy

  • Copy selector — генерирует CSS-селектор (часто переусложнённый: body > div:nth-child(3) > form > input#email);
  • Copy XPath — генерирует абсолютный XPATH (ещё хуже: /html/body/div[3]/form/input[1]).

Эти селекторы не стоит использовать «как есть». Они хрупкие. Но они — отличная отправная точка. Возьмите их, упростите, сделайте семантичнее:

Было:

#root > div > div > div:nth-child(2) > form > div:nth-child(1) > input

Стало:

form input[placeholder*='email']

Или:

//form//input[contains(@placeholder, 'email')]

13. Как выбирать локатор

Представьте: вам нужно нажать кнопку «Отправить» в форме.

  1. Откройте DevTools → Elements.

  2. Найдите кнопку в дереве (кликните по ней в интерфейсе → браузер подсветит узел).

  3. Проверьте атрибуты:

    • Есть id="submit-btn"? → By.ID;
    • Есть name="submit"? → By.NAME;
    • Есть уникальный класс class="btn-submit"? → By.CLASS_NAME или By.CSS_SELECTOR(".btn-submit");
    • Нет id/name, но есть type="submit" и value="Отправить"? → By.CSS_SELECTOR("input[type='submit'][value='Отправить']");
    • Только текст «Отправить» и тег <button>? → By.XPATH("//button[text()='Отправить']").
  4. Проверьте в строке поиска (Ctrl+F) — сколько совпадений?

    • Если 1 — локатор хороший.
    • Если >1 — уточните: добавьте предка (form button), атрибут (button.primary), текст.
  5. Запишите в код.

Этот алгоритм превращает поиск из угадывания в инженерную задачу.


14. Взаимодействие с элементами

Каждый метод объекта WebElement — это не просто вызов функции. Это инициация цепочки событий, идентичной пользовательскому действию. Разберём ключевые операции.

14.1. .click()

Когда человек кликает по кнопке, браузер генерирует последовательность событий:

  1. mousedown — кнопка мыши нажата;
  2. mouseup — кнопка отпущена;
  3. click — полный цикл завершён;
  4. (если элемент — <a> или <form>) — переход или отправка.

Selenium, вызывая element.click(), эмулирует именно эту цепочку — через драйвер и DevTools Protocol. Это важно, потому что:

  • некоторые фреймворки (например, React) слушают не click, а mousedown;
  • кастомные элементы (без <button>, а с <div onclick="...">) могут реагировать только на полный цикл;
  • элемент должен быть кликабельным: видимым, не перекрытым, не заблокированным (disabled).

Если click() вызван на элементе, который:

  • скрыт (display: none);
  • перекрыт другим слоем (z-index);
  • находится вне области просмотра без прокрутки;

— Selenium выбросит исключение ElementClickInterceptedException или ElementNotInteractableException.

Это не ошибка кода. Это сигнал: «Интерфейс изменился, и действие невозможно в текущем состоянии».

Решение — использовать явные ожидания (WebDriverWait), дожидаясь, пока элемент станет кликабельным:

wait = WebDriverWait(driver, 10)
button = wait.until(EC.element_to_be_clickable((By.ID, "submit")))
button.click()

Этот код безопасен: он ждёт до 10 секунд, пока кнопка не станет видимой и не перестанет быть перекрытой.


14.2. .send_keys("текст")

Метод send_keys() имитирует нажатие клавиш на клавиатуре. Он:

  • фокусируется на элементе (вызывает focus);
  • генерирует события keydown, keypress, keyup для каждого символа;
  • вставляет текст в значение поля (value);
  • вызывает события input и change (если текст изменился).

Это важно для SPA: React/Vue следят именно за этими событиями, чтобы обновить внутреннее состояние.

Особенности и подводные камни

  1. Поле должно быть активным и редактируемым
    — Если input имеет атрибут readonly или disabled, send_keys() вызовет InvalidElementStateException.

  2. Автозаполнение и маски
    — В полях с масками (телефон, дата) ввод send_keys("123") может преобразоваться в +7 (123) ___-__-__. Это ожидаемо и корректно.

  3. Очистка перед вводом
    send_keys() добавляет текст к текущему значению. Чтобы заменить — сначала вызовите .clear():

    field.clear()
    field.send_keys("новый текст")

    Метод .clear() эмулирует выделение всего текста (Ctrl+A) и нажатие Delete — и тоже генерирует события input/change.

  4. Специальные клавиши
    send_keys() принимает не только строки, но и константы из selenium.webdriver.common.keys:

    from selenium.webdriver.common.keys import Keys

    field.send_keys("привет")
    field.send_keys(Keys.ENTER) # имитация нажатия Enter
    field.send_keys(Keys.TAB) # переход к следующему полю
    field.send_keys(Keys.ARROW_DOWN) # стрелка вниз (для списков)

    Это позволяет эмулировать навигацию по клавиатуре — критически важно для доступности.


14.3. Чтение состояния: .text, .get_attribute(), .is_selected()

Автоматизация — это не только действия, но и проверка результатов. Selenium позволяет читать:

МетодЧто возвращаетПример применения
element.textВидимый текст внутри элемента (без скрытых, без <script>)Проверка сообщения: assert "Успешно" in message.text
element.get_attribute("атрибут")Значение HTML-атрибута: value, href, class, disabled, checkedinput.get_attribute("value") — текущее содержимое поля
element.is_selected()True/False — выбран ли чекбокс/радиокнопкаПроверка состояния: assert agree_checkbox.is_selected()
element.is_enabled()True/False — доступен ли элемент для взаимодействияКнопка disabledis_enabled() == False
element.is_displayed()True/False — отображается ли элемент (учитывает display, visibility, размеры)Проверка появления модального окна

Разница между get_attribute("value") и .text:

<input id="email" value="user@example.com">
<span id="label">Email:</span>
email_field = driver.find_element(By.ID, "email")
label = driver.find_element(By.ID, "label")

print(email_field.get_attribute("value")) # → "user@example.com"
print(label.text) # → "Email:"

Для <input>, <textarea>, <select> — значение хранится в атрибуте value. Текст внутри тега — пуст.


15. Управление формами

15.1. Чекбоксы и радиокнопки

HTML:

<input type="checkbox" id="agree" name="terms">
<input type="radio" name="plan" value="basic" checked>
<input type="radio" name="plan" value="premium">

Работа с ними:

agree = driver.find_element(By.ID, "agree")
basic_plan = driver.find_element(By.CSS_SELECTOR, "input[value='basic']")
premium_plan = driver.find_element(By.CSS_SELECTOR, "input[value='premium']")

# Проверить текущее состояние
print("Согласен:", agree.is_selected()) # → False (если не checked)
print("Тариф 'basic' выбран:", basic_plan.is_selected()) # → True

# Переключить чекбокс
if not agree.is_selected():
agree.click() # теперь is_selected() == True

# Выбрать другой тариф
if not premium_plan.is_selected():
premium_plan.click() # автоматически снимет 'basic'

Важно:

  • для чекбоксов — click() переключает состояние;
  • для радиокнопок — click() выбирает, другие с той же name автоматически отменяются;
  • не нужно читать checked через get_attribute().is_selected() надёжнее.

15.2. Выпадающие списки (<select>)

HTML:

<select id="country">
<option value="ru">Россия</option>
<option value="kz">Казахстан</option>
<option value="by">Беларусь</option>
</select>

Способ 1: вручную

dropdown = driver.find_element(By.ID, "country")
# Открыть список (не всегда нужно — можно сразу искать option)
dropdown.click()

# Найти и кликнуть нужный пункт
kazakhstan = driver.find_element(By.XPATH, "//option[@value='kz']")
kazakhstan.click()

Плюсы: полный контроль.
Минусы: хрупкость — если список не открывается кликом (например, через focus), может не сработать.

Способ 2: через класс Select

from selenium.webdriver.support.ui import Select

dropdown = Select(driver.find_element(By.ID, "country"))
dropdown.select_by_value("kz") # по value
dropdown.select_by_visible_text("Россия") # по тексту
dropdown.select_by_index(2) # по индексу (с 0)

Преимущества Select:

  • работает со всеми стандартными <select>;
  • автоматически обрабатывает открытие/закрытие списка;
  • проверяет, доступен ли нужный пункт;
  • поддерживает мультивыбор (<select multiple>).

Рекомендация: всегда используйте Select, если работаете с <select>. Это стандартный, проверенный инструмент.


16. Сложные взаимодействия: ActionChains

До сих пор мы имитировали базовые действия: клик, ввод. Но пользователь делает больше:

  • наводит курсор, чтобы открыть подменю;
  • тянет ползунок или перетаскивает карточку;
  • дважды кликает по слову;
  • прокручивает колесом;
  • зажимает Ctrl и кликает по нескольким элементам.

Для этого — ActionChains. Это строитель цепочек действий:

from selenium.webdriver.common.action_chains import ActionChains

actions = ActionChains(driver)

16.1. Наведение курсора (hover)

menu_item = driver.find_element(By.ID, "products")
submenu = driver.find_element(By.ID, "laptops")

# Навести на "Продукты", подождать появления подменю, кликнуть "Ноутбуки"
actions.move_to_element(menu_item).perform()
wait.until(EC.visibility_of(submenu))
submenu.click()

move_to_element() — ключевой метод для работы с дропдаунами, тултипами, ховер-эффектами.

16.2. Перетаскивание (drag-and-drop)

source = driver.find_element(By.ID, "card-1")
target = driver.find_element(By.ID, "trash")

actions.drag_and_drop(source, target).perform()
# или пошагово:
# actions.click_and_hold(source).move_to_element(target).release().perform()

16.3. Двойной клик, правый клик

text = driver.find_element(By.ID, "document")

actions.double_click(text).perform() # выделение слова
actions.context_click(text).perform() # ПКМ → контекстное меню

16.4. Прокрутка колесом (Selenium 4+)

actions.scroll_by_amount(0, 500).perform()     # прокрутить вниз на 500px
actions.scroll_to_element(element).perform() # прокрутить, чтобы element был виден

Это заменяет старые JavaScript-хаки и работает как настоящее движение колеса.


17. Заполнение реалистичной формы

Представим: регистрация на сайте с валидацией, чекбоксом и выпадающим списком.

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait, Select
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager

with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver:
driver.get("https://example.com/register")

wait = WebDriverWait(driver, 10)

# 1. Ждём, пока форма прогрузится
form = wait.until(EC.presence_of_element_located((By.ID, "registration-form")))

# 2. Находим поля
email = driver.find_element(By.ID, "email")
password = driver.find_element(By.NAME, "password")
country = Select(driver.find_element(By.ID, "country"))
terms = driver.find_element(By.ID, "agree-terms")
submit = driver.find_element(By.CSS_SELECTOR, "button[type='submit']")

# 3. Заполняем
email.clear()
email.send_keys("test@example.com")

password.clear()
password.send_keys("SecurePass123!")

# 4. Выбираем страну
country.select_by_visible_text("Россия")

# 5. Ставим галочку (если не стоит)
if not terms.is_selected():
terms.click()

# 6. Отправляем — с ожиданием кликабельности
wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "button[type='submit']")))
submit.click()

# 7. Проверяем результат
success = wait.until(EC.visibility_of_element_located((By.CLASS_NAME, "alert-success")))
assert "Регистрация успешна" in success.text
print("✅ Регистрация прошла успешно.")

Этот скрипт:

  • использует WebDriverWait на каждом критическом шаге;
  • проверяет состояние чекбокса вместо слепого клика;
  • читает результат, а не просто «нажимает и надеется»;
  • завершает работу через with, гарантируя закрытие браузера.

18. Навигация

Браузер — это не статичная страница. Это машина состояний, где каждое действие может изменить URL, историю, DOM. Selenium даёт полный контроль над этим процессом.

18.1. Управление историей: back(), forward(), refresh()

Эти методы эмулируют стандартные кнопки браузера:

driver.get("https://example.com/page1")
driver.get("https://example.com/page2")

driver.back() # → возвращаемся на page1
driver.forward() # → снова на page2
driver.refresh() # → перезагружаем page2

Как это работает:

  • back() и forward() используют историю сессии (session history), а не кэш страниц;
  • refresh() отправляет повторный запрос на текущий URL, с теми же заголовками и cookies;
  • после refresh() все WebElement-объекты становятся недействительными — их нужно искать заново (это StaleElementReferenceException).

Практическое применение:
— Проверка, что кнопка «Назад» не ломает приложение;
— Тестирование SPA: после back() должен восстановиться правильный маршрут;
— Возврат к форме после просмотра справки.


18.2. Текущее состояние: current_url, title, page_source

Эти свойства позволяют «осмотреться» в текущем состоянии:

СвойствоЧто возвращаетПрименение
driver.current_urlполный URL (с параметрами)Проверка редиректа: assert "success" in driver.current_url
driver.titleтекст <title>Быстрая проверка загрузки страницы
driver.page_sourceисходный HTML после обработки JavaScript (не исходный ответ сервера!)Отладка: поиск элемента, которого «не видно» в DevTools

Важно:

  • page_source — это DOM-представление, сериализованное в строку. Оно содержит изменения, внесённые JavaScript (например, динамически добавленные элементы), но не содержит содержимое <script> или комментарии, удалённые парсером;
  • page_source не обновляется автоматически — его нужно запрашивать после каждого изменения.

Пример проверки редиректа:

driver.get("https://example.com/login")
login_form = driver.find_element(By.ID, "login")
login_form.send_keys("user")
driver.find_element(By.ID, "submit").click()

# Ждём, пока URL изменится
wait.until(lambda d: "dashboard" in d.current_url)
assert "/dashboard" in driver.current_url

19. Управление окнами и вкладками

Современный пользователь редко работает с одной вкладкой. Он открывает ссылки в новых вкладках, переключается между задачами, копирует данные из одного окна в другое.

Selenium поддерживает множество окон и вкладок в рамках одной сессии.

19.1. Дескрипторы

Каждое окно или вкладка в браузере имеет уникальный идентификатор — window handle. Это строка вида "CDwindow-1234ABCD".

Основные методы:

МетодНазначение
driver.window_handlesсписок всех дескрипторов (порядок — как в интерфейсе браузера)
driver.current_window_handleдескриптор текущего окна
driver.switch_to.window(handle)переключение фокуса на окно/вкладку

19.2. Открытие новой вкладки

Есть два способа:

Способ 1: через JavaScript (универсальный)

driver.execute_script("window.open('https://example.com', '_blank');")

Это создаёт новую вкладку с указанным URL (или пустую, если '').

Способ 2: через ActionChains + клик (имитация пользователя)

link = driver.find_element(By.LINK_TEXT, "Справка")
actions = ActionChains(driver)
actions.key_down(Keys.CONTROL).click(link).key_up(Keys.CONTROL).perform()

Это эмулирует Ctrl+клик — стандартный способ открытия ссылки в новой вкладке.

19.3. Переключение между вкладками

# 1. Запоминаем дескриптор текущей вкладки
main_handle = driver.current_window_handle

# 2. Открываем новую вкладку
driver.execute_script("window.open('https://example.com');")

# 3. Получаем список всех вкладок
handles = driver.window_handles # например: ['CDwindow-1', 'CDwindow-2']

# 4. Переключаемся на новую (последнюю)
new_handle = handles[-1]
driver.switch_to.window(new_handle)

# 5. Работаем с новой вкладкой
print("Текущий URL:", driver.current_url)

# 6. Возвращаемся на основную
driver.switch_to.window(main_handle)

Важно:
— После переключения driver работает только с текущей вкладкой;
find_element() ищет в активной вкладке;
— Закрытие вкладки: driver.close() → закрывает текущую, фокус переходит на предыдущую;
— Закрытие всего браузера: driver.quit().


20. Модальные окна: alert, confirm, prompt

JavaScript позволяет показывать системные диалоги:

  • alert("Сообщение") — просто информирует;
  • confirm("Продолжить?") — «ОК» / «Отмена»;
  • prompt("Ваше имя?", "Иван") — поле ввода + «ОК» / «Отмена».

Эти окна блокируют основной поток выполнения — ни клик, ни ввод не работают, пока окно не закрыто.

Selenium предоставляет специальный интерфейс: driver.switch_to.alert.

20.1. Работа с alert

# Вызовем alert через JS
driver.execute_script("alert('Готово!');")

# Переключаемся на окно
alert = driver.switch_to.alert

# Читаем текст
print("Текст:", alert.text) # → "Готово!"

# Закрываем
alert.accept() # нажать "ОК"
# или
alert.dismiss() # нажать "Отмена" (для confirm/prompt)

20.2. Работа с prompt

driver.execute_script("prompt('Введите код:', '');")

alert = driver.switch_to.alert
alert.send_keys("12345") # ввод в поле
alert.accept() # подтвердить

20.3. Ожидание появления alert

Alert может появиться не мгновенно (например, после AJAX-запроса). Используем явное ожидание:

wait = WebDriverWait(driver, 10)
alert = wait.until(EC.alert_is_present())
alert.accept()

Это предотвращает NoAlertPresentException.


21. Прокрутка

Элемент может существовать в DOM, но быть скрытым за пределами viewport. Тогда click() или send_keys() вызовут ElementNotInteractableException.

Решение — прокрутить страницу так, чтобы элемент стал видимым.

21.1. Три стратегии прокрутки

Стратегия 1: JavaScript

element = driver.find_element(By.ID, "footer")

# Прокрутить, чтобы элемент оказался вверху viewport
driver.execute_script("arguments[0].scrollIntoView(true);", element)

# Или внизу (чтобы не прикрыть хедером):
driver.execute_script("arguments[0].scrollIntoView(false);", element)

scrollIntoView() — нативный метод DOM, работает во всех браузерах.

Стратегия 2: ActionChains

from selenium.webdriver.common.action_chains import ActionChains

actions = ActionChains(driver)
actions.scroll_to_element(element).perform() # Selenium 4.1+
# или
actions.scroll_by_amount(0, 500).perform() # на 500px вниз

Это ближе к реальному поведению пользователя.

Стратегия 3: Клавиши — для интерактивных элементов

body = driver.find_element(By.TAG_NAME, "body")
body.send_keys(Keys.PAGE_DOWN) # листать постранично
# или
body.send_keys(Keys.END) # в самый конец

Подходит для SPA, где прокрутка триггерит подгрузку.


21.2. Бесконечная прокрутка

Многие сайты (соцсети, маркетплейсы) подгружают контент при прокрутке вниз. Алгоритм сбора:

last_height = driver.execute_script("return document.body.scrollHeight")

while True:
# Прокрутить в самый низ
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

# Ждать подгрузки (5 сек или появления индикатора)
time.sleep(2)

# Проверить, изменилась ли высота
new_height = driver.execute_script("return document.body.scrollHeight")
if new_height == last_height:
break # дальше некуда
last_height = new_height

# Теперь собираем данные
items = driver.find_elements(By.CLASS_NAME, "product")
print(f"Загружено {len(items)} товаров.")

Тонкости:

  • time.sleep() — грубый способ; лучше — WebDriverWait + условие «исчез индикатор загрузки»;
  • некоторые сайты ограничивают количество подгрузок (пагинация по API внутри);
  • headless-режим может вести себя иначе — проверяйте визуально.

22. JavaScript-инъекции

Selenium — мощный инструмент, но не всесильный. Иногда нужно:

  • прочитать localStorage или sessionStorage;
  • вызвать внутренний метод SPA (например, app.router.push('/admin'));
  • обойти проверку readonly в поле;
  • изменить navigator.webdriver (этично — только для тестов!).

Для этого — driver.execute_script().

22.1. Чтение и запись в хранилища

# Получить всё localStorage как JSON
local_data = driver.execute_script("return JSON.stringify(localStorage);")
data = json.loads(local_data)

# Установить значение
driver.execute_script("localStorage.setItem('theme', 'dark');")

# Удалить
driver.execute_script("localStorage.removeItem('debug');")

Аналогично для sessionStorage.

22.2. Обход ограничений (только для тестов!)

# Снять readonly с поля
driver.execute_script("arguments[0].removeAttribute('readonly');", field)

# Заполнить напрямую (минуя события)
driver.execute_script("arguments[0].value = 'test@example.com';", email_field)

# Перезапустить событие, чтобы SPA «увидел» изменение
driver.execute_script("arguments[0].dispatchEvent(new Event('input'));", email_field)

Важно: такие приёмы нарушают принцип «тестировать как пользователь». Используйте их только когда: — интерфейс не даёт иного способа (например, поле readonly без API-доступа); — это часть теста на безопасность/валидацию; — вы чётко документируете обход.


23. Полный сценарий

Соберём заголовки новостей с сайта с бесконечной лентой (например, https://news.yandex.ru):

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from webdriver_manager.chrome import ChromeDriverManager
import time

with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver:
driver.get("https://news.yandex.ru")

wait = WebDriverWait(driver, 10)

# Дождаться первой новости
wait.until(EC.presence_of_element_located((By.CLASS_NAME, "news-item")))

# Прокрутка до конца (3 раза)
for _ in range(3):
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(1.5) # имитация загрузки

# Сбор всех заголовков
titles = driver.find_elements(By.CSS_SELECTOR, ".news-item__title")
print(f"Найдено {len(titles)} заголовков:")

for i, title in enumerate(titles[:10], 1): # первые 10
print(f"{i}. {title.text}")

# Сохранить в файл
with open("headlines.txt", "w", encoding="utf-8") as f:
for title in titles:
f.write(title.text + "\n")
print("✅ Заголовки сохранены в headlines.txt")

Этот скрипт:

  • использует WebDriverWait для старта;
  • эмулирует прокрутку с паузой;
  • собирает данные после загрузки;
  • корректно завершает работу.

24. Проблема времени

Рассмотрим типичную последовательность:

driver.get("https://spa-app.com")
button = driver.find_element(By.ID, "load-data")
button.click()
result = driver.find_element(By.ID, "output")
print(result.text)

Что может пойти не так?

  1. После get() браузер загрузил HTML, но JavaScript ещё не выполнился — кнопки #load-data нет в DOM → NoSuchElementException.
  2. После click() запущен AJAX-запрос, но ответ ещё не пришёл — элемент #output пуст или отсутствует.
  3. Ответ пришёл, но React ещё не обновил DOM — result.text пуст, хотя через 200 мс там будет «Готово».

Это не ошибки кода. Это асинхронная природа веба. Браузер — не машина состояний, а оркестр параллельных процессов: сеть, парсинг, рендеринг, выполнение скриптов. Selenium должен уметь слушать, когда нужный момент наступил.


25. Два типа ожиданий: неявные и явные

Selenium предлагает два подхода к ожиданию. Они различаются по механизму, гибкости и зоне ответственности.

25.1. Неявные ожидания (implicitly_wait)

driver.implicitly_wait(10)  # секунды

Это настройка по умолчанию для всех операций поиска (find_element, find_elements). Если элемент не найден сразу, Selenium будет повторять попытку каждые 500 мс, пока не пройдёт указанное время.

Пример:

driver.implicitly_wait(10)
element = driver.find_element(By.ID, "delayed-button") # ждёт до 10 сек

Преимущества:

  • простота: одна строка — и все поиски «умные»;
  • не нужно оборачивать каждый find_element в try/except.

Ограничения:

  • глобальный эффект: действует на всю сессию. Если один тест требует 30 сек, а другой — 2, приходится сбрасывать (driver.implicitly_wait(0));
  • не поддерживает сложные условия: нельзя дождаться, пока кнопка станет кликабельной, пока текст содержит «Успешно», пока исчезнет индикатор загрузки;
  • работает только для поиска элементов, не для проверки их состояния.

Рекомендация: не используйте implicitly_wait в новых проектах. Он устарел как основной механизм. Его место — в legacy-коде или очень простых скриптах.


25.2. Явные ожидания (WebDriverWait)

Это целевой, декларативный подход: «подожди, пока не выполнится условие X».

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, timeout=10, poll_frequency=0.5)
element = wait.until(EC.presence_of_element_located((By.ID, "submit")))

Как это работает:

  1. WebDriverWait создаёт «наблюдателя» с таймаутом и частотой опроса;
  2. until() запускает цикл: каждые 0.5 сек вызывает переданное условие;
  3. Если условие возвращает истинное значение — возвращает результат;
  4. Если за 10 сек условие так и не выполнилось — TimeoutException.

Преимущества:

  • гибкость: можно ждать любое состояние — видимости, кликабельности, текста, URL, alert’а;
  • локальность: каждое ожидание настраивается под конкретную задачу;
  • читаемость: код сам объясняет что ждёт, а не просто сколько;
  • отладка: при падении видно, какое условие не выполнилось.

Это рекомендуемый способ в 2025 году.


26. Библиотека условий: expected_conditions

Модуль selenium.webdriver.support.expected_conditions (сокращённо EC) содержит десятки предопределённых условий. Вот самые важные.

26.1. Условия для элементов

УсловиеКогда полезно
presence_of_element_located(locator)Элемент появился в DOM (но может быть скрыт)
visibility_of_element_located(locator)Элемент видим: не display:none, не visibility:hidden, размеры > 0
element_to_be_clickable(locator)Элемент видим и не перекрыт, не disabled — можно кликать
text_to_be_present_in_element(locator, "текст")В тексте элемента есть подстрока (регистрозависимо)
text_to_be_present_in_element_value(locator, "значение")В value поля ввода есть подстрока (для <input>)

Примеры:

# Ждём, пока кнопка появится и станет кликабельной
button = wait.until(EC.element_to_be_clickable((By.ID, "pay-now")))

# Ждём сообщения об успехе
success = wait.until(
EC.text_to_be_present_in_element((By.CLASS_NAME, "alert"), "Оплата прошла")
)

# Ждём, пока поле загрузится данными
wait.until(
EC.text_to_be_present_in_element_value((By.ID, "user-email"), "@")
)

26.2. Условия для страницы

УсловиеПрименение
title_is("Точное название")Проверка загрузки конкретной страницы
title_contains("часть")Гибкая проверка: «Корзина — Магазин»
url_to_be("https://exact.com")Точный URL
url_contains("/checkout")Часть пути или параметра
url_matches("regex")Полный контроль через регулярные выражения

Пример:

wait.until(EC.url_contains("/order/confirmed"))
assert "Спасибо за заказ" in driver.title

26.3. Условия для модальных окон и фреймов

УсловиеНазначение
alert_is_present()Ждём появления alert, confirm, prompt
frame_to_be_available_and_switch_to_it(locator)Ждём iframe и автоматически переключаемся в него

Пример:

wait.until(EC.alert_is_present())
driver.switch_to.alert.accept()

27. Пользовательские условия

Иногда нужно дождаться чего-то уникального:

  • пока в localStorage появится ключ auth_token;
  • пока на странице будет ровно 12 карточек товаров;
  • пока сумма в корзине станет больше 1000.

Для этого — кастомные условия.

Условие — это callable, который принимает driver и возвращает:

  • True / объект — успех;
  • False / None — продолжить ожидание;
  • исключение (кроме NoSuchElementException) — прервать сразу.

27.1. Ожидание количества элементов

def elements_count_to_be(locator, count):
def _predicate(driver):
elements = driver.find_elements(*locator)
return elements if len(elements) >= count else False
return _predicate

# Ждём, пока появится хотя бы 5 товаров
cards = wait.until(elements_count_to_be((By.CLASS_NAME, "product"), 5))
print(f"Загружено {len(cards)} карточек")

27.2. Проверка localStorage

def local_storage_contains_key(key):
def _predicate(driver):
script = f"return localStorage.getItem('{key}') !== null;"
return driver.execute_script(script)
return _predicate

wait.until(local_storage_contains_key("userSession"))

27.3. Элемент исчез (например, индикатор загрузки)

def element_to_disappear(locator):
def _predicate(driver):
try:
element = driver.find_element(*locator)
return not element.is_displayed()
except NoSuchElementException:
return True # элемента нет — условие выполнено
return _predicate

wait.until(element_to_disappear((By.CLASS_NAME, "spinner")))

28. Стабильный сценарий с ожиданиями на каждом этапе

Авторизация в SPA с динамической загрузкой:

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait, Select
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager

with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver:
wait = WebDriverWait(driver, 15)

# 1. Открыть и дождаться загрузки SPA
driver.get("https://app.example.com/login")
wait.until(EC.title_contains("Вход")) # SPA мог переопределить title позже

# 2. Дождаться формы (может инициализироваться асинхронно)
form = wait.until(EC.presence_of_element_located((By.ID, "login-form")))

# 3. Заполнить поля
email = wait.until(EC.element_to_be_clickable((By.ID, "email")))
password = driver.find_element(By.ID, "password") # уже есть, если форма загружена

email.clear()
email.send_keys("user@test.com")
password.send_keys("pass123")

# 4. Отправить — и дождаться редиректа
submit = driver.find_element(By.CSS_SELECTOR, "button[type='submit']")
submit.click()

# Ждём, пока URL изменится на /dashboard
wait.until(EC.url_contains("/dashboard"))

# 5. Проверяем, что пользователь вошёл
user_menu = wait.until(EC.visibility_of_element_located((By.ID, "user-menu")))
assert "user@test.com" in user_menu.text

print("✅ Авторизация успешна.")

Этот скрипт:

  • не использует time.sleep() — ожидания адаптивны;
  • каждое действие сопровождается проверкой готовности;
  • устойчив к задержкам сети и инициализации SPA;
  • читаем и самодокументируем.

29. Типичные ошибки и как их избежать

ОшибкаПричинаРешение
NoSuchElementExceptionЭлемент ещё не в DOMИспользовать presence_of_element_located или visibility_of...
ElementNotInteractableExceptionЭлемент скрыт, перекрыт, не кликабельныйelement_to_be_clickable + прокрутка
StaleElementReferenceExceptionЭлемент был удалён/пересоздан после поискаИскать элемент непосредственно перед использованием, не кэшировать надолго
Скрипт «зависает» на 10+ секТаймаут завышен, условие никогда не выполнитсяУменьшить timeout, добавить логирование, проверить локатор
Условие «выполняется», но текст пустЖдали presence, а не visibility или text_to_be_presentУточнить условие: текст может появиться позже, чем элемент

Золотое правило:

Если действие требует взаимодействия с элементом — сначала дождитесь, пока он станет пригодным для этого действия.


30. Cookies

Cookies — это небольшие фрагменты данных, которые сервер отправляет браузеру через заголовок Set-Cookie, а браузер возвращает при каждом последующем запросе к тому же домену. Они лежат в основе:

  • аутентификации (сессионные cookies, JWT в HttpOnly);
  • персонализации (язык, регион, тема);
  • аналитики (идентификаторы посещений);
  • защиты (CSRF-токены, SameSite).

30.1. Чтение cookies в Selenium

Selenium даёт полный доступ к cookies текущего домена:

# Все cookies как список словарей
cookies = driver.get_cookies()
# [
# {'name': 'sessionid', 'value': 'abc123', 'domain': '.example.com', 'path': '/', 'secure': True, 'httpOnly': True},
# {'name': 'lang', 'value': 'ru', 'domain': '.example.com', 'path': '/', 'secure': False}
# ]

# Одна cookie по имени
session_cookie = driver.get_cookie("sessionid") # или None, если нет

# Конкретное значение
session_id = driver.get_cookie("sessionid")["value"] if driver.get_cookie("sessionid") else None

Каждая cookie содержит:

  • name, value — обязательные;
  • domain, path — область действия;
  • secure — только по HTTPS;
  • httpOnly — недоступна из JavaScript (защита от XSS);
  • expiry — время жизни (Unix timestamp).

30.2. Установка и удаление cookies

# Добавить cookie (должна соответствовать текущему domain/path)
driver.add_cookie({
"name": "lang",
"value": "en",
"domain": ".example.com",
"path": "/",
"secure": False
})

# Удалить одну
driver.delete_cookie("lang")

# Удалить все
driver.delete_all_cookies()

Важно:
add_cookie() можно вызывать только после открытия страницы на нужном домене (иначе InvalidCookieDomainException);
httpOnly cookies невозможно прочитать через JavaScript, но Selenium видит их — это преимущество перед клиентскими скриптами.


31. Восстановление сессии без повторного логина

Представим: вы тестируете админку, и каждый вход через форму занимает 10 секунд. Можно ли «войти раз — и использовать сессию в 100 тестах»?

Да. Алгоритм:

  1. Один раз — выполнить логин вручную или через скрипт;
  2. Сохранить cookies в файл;
  3. В каждом последующем тесте — загрузить cookies до открытия страницы.

31.1. Сохранение сессии

import json

with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver:
driver.get("https://example.com/login")
# ... выполнить логин

# Дождаться успешного входа
wait.until(EC.url_contains("/dashboard"))

# Сохранить cookies
cookies = driver.get_cookies()
with open("session.json", "w", encoding="utf-8") as f:
json.dump(cookies, f, indent=2)
print("✅ Сессия сохранена в session.json")

31.2. Восстановление сессии

with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver:
# Сначала открыть домен (иначе add_cookie не сработает)
driver.get("https://example.com")

# Загрузить и установить cookies
with open("session.json", encoding="utf-8") as f:
cookies = json.load(f)
for cookie in cookies:
# Удаляем expiry, если оно прошло (иначе ошибка)
if 'expiry' in cookie:
del cookie['expiry']
driver.add_cookie(cookie)

# Обновить страницу — теперь сервер увидит сессию
driver.refresh()

# Проверка
assert "/dashboard" in driver.current_url
print("✅ Сессия восстановлена. Вход выполнен без формы.")

Преимущества:

  • ускорение тестов в 3–5 раз;
  • обход многофакторной аутентификации (если сессия уже живая);
  • стабильность: не зависит от изменений в форме логина.

Ограничения:

  • cookies имеют срок жизни (обычно 30 минут–24 часа);
  • сервер может привязывать сессию к IP/User-Agent — тогда восстановление с другого устройства не сработает;
  • httpOnly cookies передаются корректно — Selenium умеет с ними работать.

32. localStorage и sessionStorage

Современные приложения (React, Vue, Angular) всё чаще хранят состояние не на сервере, а в браузере:

  • localStorage — постоянное, до очистки вручную;
  • sessionStorage — живёт, пока открыта вкладка.

Типичные данные:

  • токены авторизации (если не httpOnly);
  • настройки интерфейса (тема, язык);
  • кэш данных (списки, конфигурации);
  • черновики форм.

32.1. Чтение и запись через JavaScript

Selenium не имеет прямых методов для localStorage, но может выполнить JS:

# Получить всё localStorage как JSON-строка
local_data = driver.execute_script("return JSON.stringify(localStorage);")
data = json.loads(local_data) if local_data else {}

# Получить одно значение
theme = driver.execute_script("return localStorage.getItem('theme');")

# Установить
driver.execute_script("localStorage.setItem('theme', 'dark');")

# Удалить
driver.execute_script("localStorage.removeItem('debug');")

# Очистить
driver.execute_script("localStorage.clear();")

Аналогично для sessionStorage — замените localStoragesessionStorage.

32.2. Сохранение состояния SPA

Допустим, приложение хранит токен в localStorage:

# После логина — сохраняем
token = driver.execute_script("return localStorage.getItem('authToken');")
if token:
with open("auth_token.txt", "w") as f:
f.write(token)

# В новом сценарии — восстанавливаем
with open("auth_token.txt") as f:
token = f.read().strip()

driver.get("https://spa-app.com")
driver.execute_script(f"localStorage.setItem('authToken', '{token}');")
driver.refresh()

# Проверяем — пользователь в системе
wait.until(EC.presence_of_element_located((By.ID, "user-panel")))

Важно:
— Такой подход менее безопасен, чем cookies с httpOnly;
— Он уместен только для тестов, где вы контролируете окружение;
— В продакшене никогда не храните секреты в localStorage.


33. Профили браузера

Каждый запуск webdriver.Chrome() по умолчанию использует временный профиль — пустой, без истории, cookies, расширений. Это хорошо для изоляции тестов, но плохо, если нужно:

  • сохранить настройки между запусками;
  • использовать расширения (AdBlock, тестовые инструменты);
  • эмулировать «реального пользователя» с историей.

33.1. Создание постоянного профиля

from selenium.webdriver.chrome.options import Options

options = Options()
# Указать папку профиля (если не существует — создастся)
options.add_argument("--user-data-dir=/home/user/selenium-profile")
# Указать имя профиля (по умолчанию "Default")
options.add_argument("--profile-directory=TestProfile")

driver = webdriver.Chrome(service=Service(...), options=options)

Теперь:

  • все данные (cookies, кэш, localStorage) сохраняются между запусками;
  • можно вручную настроить браузер один раз (войти, выбрать тему, установить расширения);
  • последующие скрипты будут работать в этом контексте.

33.2. Использование существующего профиля (например, вашего основного)

⚠️ Осторожно: не используйте основной профиль в автоматических тестах — можно повредить данные.

# Узнать путь: chrome://version → "Папка профиля"
# Пример для Linux:
options.add_argument("--user-data-dir=/home/user/.config/google-chrome")
options.add_argument("--profile-directory=Default")

33.3. Headless + профиль — для CI

В CI-средах можно комбинировать:

options.add_argument("--headless=new")
options.add_argument("--user-data-dir=/tmp/chrome-profile")

Это даёт изолированный, быстрый, но «персонализированный» браузер.


34. Когда и как использовать сохранение состояния

СценарийРекомендация
Тестирование логинаНикогда не восстанавливайте сессию — тестируйте полный цикл
Тестирование внутренних страницВосстанавливайте сессию — экономия времени, фокус на логике
Сбор данных с личного кабинетаТолько если вы — владелец аккаунта, и это не нарушает ToS
Мониторинг цен/наличияПредпочтительно — через API; если через браузер — используйте отдельный учётный запись
Обход платных функцийНедопустимо — нарушает условия использования и закон

Юридический контекст (РФ):
— Статья 137 УК РФ (нарушение неприкосновенности частной жизни);
— Федеральный закон №152-ФЗ «О персональных данных»;
— Гражданский кодекс (нарушение условий публичной оферты — ToS сайта).

Правило: если сайт требует авторизацию для доступа к данным — это сигнал: эти данные не предназначены для автоматического сбора без явного разрешения.


35. Комплексный сценарий — мониторинг личного кабинета

Цель: раз в час проверять баланс на сайте банка (только для своего аккаунта, с согласия).

import json
import time
from datetime import datetime

def load_session():
with open("bank_session.json") as f:
return json.load(f)

def save_session(driver):
cookies = driver.get_cookies()
# Удаляем expiry для будущих запусков
for c in cookies:
c.pop("expiry", None)
with open("bank_session.json", "w") as f:
json.dump(cookies, f)

def check_balance():
options = Options()
options.add_argument("--headless=new") # без GUI
with webdriver.Chrome(service=Service(...), options=options) as driver:
driver.get("https://bank.example.com")

# Восстановить сессию
for cookie in load_session():
driver.add_cookie(cookie)
driver.refresh()

# Дождаться загрузки
wait = WebDriverWait(driver, 20)
balance_el = wait.until(EC.visibility_of_element_located((By.ID, "balance")))
balance = balance_el.text.replace("₽", "").replace(" ", "")

print(f"[{datetime.now()}] Баланс: {balance} ₽")
return float(balance)

# Первичная настройка (вручную!)
# 1. Запустить браузер без headless
# 2. Войти вручную
# 3. Вызвать save_session(driver)

# Регулярная проверка
while True:
try:
check_balance()
except Exception as e:
print("❌ Ошибка:", e)
time.sleep(3600) # 1 час

Этот скрипт:

  • работает в фоне;
  • не хранит пароль — только cookies;
  • логирует состояние;
  • устойчив к мелким изменениям интерфейса (ожидания).

36. Headless-режим

Headless-режим — это запуск браузера без графического интерфейса. Он работает «внутри себя», рисуя страницы в памяти, но не выводя их на экран.

36.1. Как включить (Selenium 4.13+, Chrome 109+)

from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument("--headless=new") # современный headless (Chromium)
# options.add_argument("--headless") # старый режим (устарел в 2023)

driver = webdriver.Chrome(options=options, service=...)

Важно:
--headless=new использует ту же архитектуру рендеринга, что и обычный режим — поведение идентично;
— Старый --headless (без =new) эмулирует устаревшую версию и не поддерживается в новых Chrome.

36.2. Преимущества headless-режима

  • Скорость: на 15–30% быстрее из-за отсутствия отрисовки пикселей;
  • Экономия ресурсов: меньше потребление CPU и RAM (на 100–200 МБ на сессию);
  • Совместимость с серверами: работает на Linux без GUI (например, в Docker, VPS, CI);
  • Параллелизм: можно запустить 10, 20, 50 сессий на одном сервере.

36.3. Ограничения и подводные камни

  1. Детектирование сайтом
    Многие ресурсы (особенно с защитой от ботов) проверяют:

    • navigator.webdriver === true;
    • отсутствие window.outerWidth, window.outerHeight;
    • нестандартные значения navigator.plugins, navigator.languages.

    Результат: блокировка, CAPTCHA, пустая страница.

  2. Разное поведение JavaScript
    Некоторые фреймворки (редко) используют requestAnimationFrame или screen.width для адаптивности — в headless эти значения могут отличаться.

  3. Отладка сложнее
    Нельзя «посмотреть глазами». Приходится делать скриншоты:

    driver.save_screenshot("debug.png")

36.4. Headless в Linux/Arch (без GUI)

На сервере без графической оболочки нужно:

  1. Установить Chrome и драйвер (например, через AUR: yay -S google-chrome chromedriver);
  2. Убедиться, что установлен xvfb (X Virtual Framebuffer) — не обязательно при --headless=new;
  3. Или использовать официальный Docker-образ (см. ниже).

Современный headless (--headless=new) не требует X11, так как рендеринг идёт через --disable-gpu и --no-sandbox (но sandbox лучше не отключать без необходимости).


37. Маскировка Selenium

⚠️ Важно: эти методы не для обхода защиты, а для того, чтобы тесты не падали из-за ложного срабатывания систем обнаружения. Использование в целях обхода платных сервисов, сбора персональных данных или нарушения ToS — недопустимо.

37.1. Устранение navigator.webdriver

Самый простой признак:

driver.get("https://example.com")
print(driver.execute_script("return navigator.webdriver")) # → True

Исправление:

options.add_argument("--disable-blink-features=AutomationControlled")
driver = webdriver.Chrome(options=options, ...)

# После запуска — удалить свойство
driver.execute_script("delete navigator.__proto__.webdriver")

Теперь navigator.webdriver возвращает undefined.

37.2. Подмена User-Agent

User-Agent — строка, идентифицирующая браузер, ОС, устройство.

По умолчанию Selenium использует:

Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/131.0.6778.85 Safari/537.36

Видно: HeadlessChrome → сигнал тревоги.

Подмена:

ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
options.add_argument(f"--user-agent={ua}")

Лучше брать актуальный UA с whatmyuseragent.com или использовать библиотеку fake-useragent.

37.3. Дополнительные аргументы

options.add_argument("--disable-infobars")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--no-sandbox")
options.add_argument("--disable-gpu") # для некоторых серверов
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)

Эти настройки:

  • убирают панель «Chrome управляется автоматизированным ПО»;
  • снижают потребление /dev/shm;
  • отключают sandbox (только в доверенной среде!);
  • удаляют следы расширения ChromeDriver.

Важно: --no-sandbox снижает безопасность. Используйте только в изолированных средах (Docker, CI).


38. Прокси

Прокси-сервер выступает посредником между браузером и интернетом. В Selenium его можно настроить:

options.add_argument("--proxy-server=http://192.168.1.10:8080")
# или
options.add_argument("--proxy-server=socks5://user:pass@proxy.example.com:1080")

38.1. Зачем использовать прокси

  • Обход геоограничений (например, доступ к региональному контенту);
  • Анонимизация (скрытие реального IP);
  • Логирование и отладка трафика (через Charles Proxy, Fiddler);
  • Тестирование в условиях слабого интернета (прокси с искусственной задержкой).

38.2. Ограничения и риски

  • Безопасность: трафик через HTTP-прокси передаётся в открытом виде;
  • Совместимость: не все сайты работают через прокси (особенно с WebSockets);
  • Юридические последствия:
    — В РФ использование прокси для обхода блокировок или платных сервисов может нарушать ст. 273 УК РФ (создание/использование вредоносных программ) и условия хостинга;
    — Использование публичных бесплатных прокси — высокий риск утечки cookies и сессий.

Рекомендация:
— Для тестов — используйте локальный прокси (например, mitmproxy);
— Никогда не передавайте учётные данные через публичные прокси;
— Всегда проверяйте, разрешено ли использование прокси в ToS сайта.


39. Расширения

Расширения (.crx, .zip) можно загрузить при запуске:

options.add_extension("/path/to/adblock.crx")
# или распакованное расширение:
options.add_argument("--load-extension=/path/to/unpacked-extension")

Применение

  • Отключение рекламы — ускоряет загрузку, упрощает парсинг;
  • Тестовые расширения — например, «React Developer Tools» для отладки SPA;
  • Кастомные инструменты — например, расширение для инъекции тестовых данных.

Ограничения

  • Расширения увеличивают время запуска (на 1–3 сек);
  • Могут конфликтовать с тестируемым приложением;
  • В headless-режиме не все расширения работают (особенно те, что требуют GUI);
  • Для Chrome в CI — лучше использовать --disable-extensions по умолчанию, включая только нужные.

40. Запуск в Docker

Docker — стандарт для запуска Selenium в CI/CD и распределённых системах.

40.1. Официальный образ Selenium

Selenium предоставляет готовые образы:

docker run -d -p 4444:4444 --shm-size="2g" selenium/standalone-chrome:latest

После этого можно подключаться удалённо:

driver = webdriver.Remote(
command_executor="http://localhost:4444",
options=Options()
)

Преимущества:

  • одинаковое окружение на всех машинах;
  • параллельный запуск через Selenium Grid;
  • автоматическое управление памятью (--shm-size критичен для Chrome).

40.2. Собственный Dockerfile

Для тонкой настройки:

FROM python:3.11-slim

# Установка Chrome
RUN apt-get update && apt-get install -y \
wget gnupg2 \
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
&& echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list \
&& apt-get update \
&& apt-get install -y google-chrome-stable \
&& rm -rf /var/lib/apt/lists/*

# Установка Python-зависимостей
COPY requirements.txt .
RUN pip install -r requirements.txt

COPY script.py .
CMD ["python", "script.py"]

Запуск:

docker build -t selenium-job .
docker run --shm-size=2g selenium-job

Важно:
--shm-size=2g предотвращает падение Chrome из-за нехватки разделяемой памяти;
— В CI (GitHub Actions, GitLab CI) это стандартная практика.


41. Производительность

Один экземпляр Chrome в headless-режиме потребляет:

РесурсМинимумТипичноС учётом тяжёлого SPA
RAM200 МБ350 МБ600+ МБ
CPU0.1 ядро0.3 ядра0.8+ ядра (при активности)

На сервере с 8 ГБ RAM можно запустить ~20 параллельных сессий (с запасом под ОС и драйверы).

Оптимизации:

  • использовать --headless=new;
  • закрывать драйвер через with или quit();
  • не хранить WebElement дольше необходимого;
  • избегать page_source в циклах — это сериализация всего DOM.